package com.ease.util.collections {
   import com.ease.util.Set;
   import flash.utils.Dictionary;


   /**
    * Maps keys to a mix of objects and collections of objects. It's particularly useful
    * when a key can map to either an object of some type, or multiple objects of that type
    * (a composite, so to speak).
    *
    * <p>Weak-referencing applies only to the <i>key</i> part of the key-value pairs in this
    * map. Values are always strong-referenced.
    */
   public class CompositeMap
   {
      private var _objectsByKey:Dictionary;
      private var _objects:Set/*Object*/;

      /**
       * Initializes an instance of this class.
       */
      public function CompositeMap( useWeakReferences:Boolean = false ) {
         _objectsByKey = new Dictionary( useWeakReferences );
         _objects = new Set();
      }

      //-------------------------------------------------------
      // Property: length

      /** The number of objects in this map. */
      public function get length():int {
         return _objects.length;
      }

      /**
       * Returns all the values currently mapped to the given key. Returns all values if the
       * provided key is null.
       */
      public final function getValues( key:* = null ):Array {
         if (key == null) {
            var vals:Array/*Object*/ = _objects.toArray();
            return vals;
         }
         var value:Object = _objectsByKey[key];
         if (value is Array) {
            return value as Array;
         }
         if (value) {
            return [value];
         }
         return [];
      }

      /**
       * Indicates whether or not the given object is contained by this map as a value.
       */
      public final function contains( value:Object ):Boolean {
         return _objects.contains( value );
      }

      /**
       * Adds a new key-value pair. <code>key</code> must be non-null.
       */
      public final function add( key:*, value:* ):void {
         if (key == null) {
            return;
         }

         _objects.add( value );
         var currentVal:Object = _objectsByKey[key];
         if (currentVal is Array) {
            var arr:Array = currentVal as Array;
            if (arr.indexOf( value ) == -1) {
               arr.push( value );
            }
            return;
         }
         if (currentVal != null && currentVal != value) {
            _objectsByKey[key] = [currentVal, value];
            return;
         }
         _objectsByKey[key] = value;
      }

      /**
       * Removes an existing key-value pair.
       */
      public final function removeValue( key:*, value:* ):void {
         if (key == null) {
            return;
         }

         // If the object was already removed from the main object map, this is fine.
         _objects.remove( value );

         // Search to remove it from the array of object corresponding to the key.
         var currentVal:Object = _objectsByKey[key];
         if (currentVal == value) {
            delete _objectsByKey[key];
            return;
         }
         if (currentVal is Array) {
            var objArray:Array = currentVal as Array;
            var index:int = objArray.indexOf( value );
            if (index < 0) {
               return;
            }
            objArray.splice( index, 1 );
            if (objArray.length == 0) {
               delete _objectsByKey[key];
            }
         }
      }
   } // class

} // package
